#include <queue>
#include <condition_variable>
#include <cassert>
#include <mutex>
#include <iostream>
#include <malloc.h>


using namespace std::chrono;

class BufferPool
{
public:
    BufferPool(uint32_t elementSize, uint32_t elementCount)
    {
        assert(elementSize > 0 && elementCount > 0);

        m_elementSize = elementSize;
        m_elementCount = elementCount;

        void* buffer;
        for (uint32_t i = 0; i < m_elementCount; i++)
        {
            buffer = malloc(m_elementSize);
            m_queue.push(buffer);
        }
    }


    ~BufferPool()
    {
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            if (!m_condVar.wait_for(lock, 60s, [this] { return m_queue.size() == m_elementCount; }))
            {
                std::cout << "[Warning]: m_queue is not full, some element may lose somewhere outside." << std::endl;
            }
        }

        void* buffer;
        uint32_t size = m_queue.size();
        for (uint32_t i = 0; i < size; i++)
        {
            buffer = m_queue.front();
            free(buffer);
            m_queue.pop();
        }
    }


    void Push(void* buffer)
    {
        assert(!Full());

        std::lock_guard<std::mutex> lock(m_mutex);
        m_queue.push(buffer);
        m_condVar.notify_one();
    }


    void* Pop()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (!m_condVar.wait_for(lock, 60s, [this] { return !m_queue.empty(); }))
        {
            std::cout << "[Error]: m_queue is empty, pop null element." << std::endl;
            return nullptr;
        }

        void* buffer = m_queue.front();
        m_queue.pop();
        return buffer;
    }
    bool Full()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return (m_queue.size() == m_elementCount);
    }
    uint32_t ElementSize()
    {
        return m_elementSize;
    }
    uint32_t ElementCount()
    {
        return m_elementCount;
    }
    uint32_t Size()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_queue.size();
    }

protected:
    std::mutex m_mutex;
    std::condition_variable m_condVar;
    std::queue<void*> m_queue;

    uint32_t m_elementSize;
    uint32_t m_elementCount;
}; // BufferPool
